﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;

namespace Afiniczny
{
    class Decrypter
    {
        private CipherAffineAlgorithm _cipherAffineAlgorithm;
        private int _a, _b;
        private int _blockSize;
        private int alphabetLenght;

        public Decrypter(CipherAffineAlgorithm cipherAffineAlgorithm)
        {
            _cipherAffineAlgorithm = cipherAffineAlgorithm;
            _a = _cipherAffineAlgorithm.GetKeyA();
            _b = _cipherAffineAlgorithm.GetKeyB();
            _blockSize = _cipherAffineAlgorithm.GetBlockSize();
            alphabetLenght = Alphabet.getAlphabetLenght();
        }

        public BigInteger[] GetBlocksFromText(string a_text)
        {
            string[] stringBlocks = a_text.Split('|');
            BigInteger[] blocks = new BigInteger[stringBlocks.Length];
            for (int i = 0; i < stringBlocks.Length; i++)
            {
                blocks[i] = _cipherAffineAlgorithm.GetBlockFromText(stringBlocks[i]);
            }
            return blocks;
        }

        public BigInteger DecryptBlock(BigInteger a_codedBlockValue)
        {
            BigInteger invA = ModInverse(_a, BigInteger.Pow(alphabetLenght, _blockSize));
            return (invA * (a_codedBlockValue - _b)) % BigInteger.Pow(alphabetLenght, _blockSize);
        }

        public string DecryptText(string cryptedText)
        {
            string result = "";
            foreach (var block in GetBlocksFromText(cryptedText))
            {
                result += _cipherAffineAlgorithm.DecodeBlockToString(DecryptBlock(block));
            }

            return result;
        }

        private BigInteger ModInverse(BigInteger a, BigInteger b)
        {
            BigInteger dividend = a % b;
            BigInteger divisor = b;

            BigInteger last_x = BigInteger.One;
            BigInteger curr_x = BigInteger.Zero;

            while (divisor.Sign > 0)
            {
                BigInteger quotient = dividend / divisor;
                BigInteger remainder = dividend % divisor;
                if (remainder.Sign <= 0)
                {
                    break;
                }

                /* This is quite clever, in the algorithm in form
                 * ax + by = gcd(a, b) we only keep track of the
                 * value curr_x and the last_x from last iteration,
                 * the y value is ignored anyway. After remainder
                 * runs to zero, we get our inverse from curr_x */
                BigInteger next_x = last_x - curr_x * quotient;
                last_x = curr_x;
                curr_x = next_x;

                dividend = divisor;
                divisor = remainder;
            }

            if (divisor != BigInteger.One)
            {
                throw new Exception("Numbers a and b are not relatively primes");
            }
            return (curr_x.Sign < 0 ? curr_x + b : curr_x);
        }
    }
}
